package sketch.shape;

import sketch.shape.domain.Link;
import sketch.shape.domain.LinkEnd;
import sketch.shape.geom.Diamond;
import sketch.shape.geom.Rectangle;
import sketch.shape.geom.Triangle;
import sketch.shape.geom.prim.Line;
import sketch.shape.geom.prim.Point;

public class ShapePredicate {

	private ShapePredicate() {}

	// All predicates should be written as predicateArgtype1Argtype2... form
	/*
	 * common
	 */
//	public static boolean contains(Shape sh1, Shape sh2) {
//		if (sh1 instanceof Line) {
//			Line l1 = (Line) sh1;
//
//			if (sh2 instanceof Line) {
//				Line l2 = (Line) sh2;
//
//				return contains(l1, l2);
//			}
//		}
//
//		return false;
//	}

	public static boolean equalLength(double length1, double length2) {
		return Math.abs(length1 - length2) < ShapeThreshold.lengthDis;
	}

	public static boolean equalAngle(double angle1, double angle2) {
//		System.out.println(angle1 + ":" + angle2);
		return Math.abs(angle1 - angle2) < ShapeThreshold.angleDis;
	}

	/*
	 * Line
	 */
	public static boolean horizontalLine(Line line) {
		double angle = angleLineLine(line, new Line(0,0, 10,0));
		return Math.abs(angle) < ShapeThreshold.angleDis ||
			   Math.abs(angle - Math.PI) < ShapeThreshold.angleDis;
	}

	public static boolean verticalLine(Line line) {
		double angle = angleLineLine(line, new Line(0,0, 10,0));
		return Math.abs(Math.PI * .5 - angle) < ShapeThreshold.angleDis;
	}

	/*
	 * Line - Line
	 */
	public static boolean parallelLineLine(Line l1, Line l2) {
//		gVector v1 = l1.dir();
//		gVector v2 = l2.dir();
//
//		double angle = v1.angleWith(v2);
////		System.out.println(angle);
//		if (angle < ShapeThreshold.angleDis ||
//			Math.abs(Math.PI - angle) < ShapeThreshold.angleDis ||
//			Math.abs(Math.PI * 2 - angle) < ShapeThreshold.angleDis)
//			return true;
//		return false;
		double angle = l1.angleWith(l2);
		return equalAngle(angle, Math.PI) ||
			   equalAngle(angle, 0);
	}

	public static boolean orthogonalLineLine(Line l1, Line l2) {
//		gVector v1 = l1.dir();
//		gVector v2 = l2.dir();
//
//		double angle = v1.angleWith(v2);
////		System.out.println(angle);
//		if (Math.abs(Math.PI * 0.5 - angle) < ShapeThreshold.angleDis ||
//			Math.abs(Math.PI * 1.5 - angle) < ShapeThreshold.angleDis)
//			return true;
//		return false;
		return equalAngle(l1.angleWith(l2), Math.PI * 0.5);
	}

	public static boolean reverseLineLine(Line l1, Line l2) {
		return equalAngle(l1.angleDWith(l2), Math.PI);
	}

	public static boolean identicalLineLine(Line l1, Line l2) {
		return (identicalPointPoint(l1.getP1(), l2.getP1()) &&
			   identicalPointPoint(l1.getP2(), l2.getP2())) ||
			   (identicalPointPoint(l1.getP1(), l2.getP2()) &&
			   identicalPointPoint(l1.getP2(), l2.getP1()));
	}

	public static boolean equalLengthLineLine(Line l1, Line l2) {
		return Math.abs(l1.length() - l2.length()) < ShapeThreshold.lengthDis;
	}

	public static boolean longerLengthLineLine(Line l1, Line l2) {
		return l1.length() > (l2.length() + ShapeThreshold.lengthDis);
	}

	public static boolean shorterLengthLineLine(Line l1, Line l2) {
		return (l1.length() + ShapeThreshold.lengthDis) < l2.length();
	}

	public static boolean containsLineLine(Line l1, Line l2) {
		return containsLinePoint(l1, l2.getP1()) &&
		       containsLinePoint(l1, l2.getP2());
	}

	public static boolean acrossLineLine(Line l1, Line l2) {
		return l1.intersectsLine(l2);
	}

	public static boolean overlappedLineLine(Line l1, Line l2) {
		/*
		 * When l1 l2 are overlapped
		 * then make l1.getP2() on l2 and l2.getP1() on l1
		 *
		 * The rule MUST be applied
		 *
		 * FAILED!!!
		 */
		if (!parallelLineLine(l1, l2))
			return false;

		boolean b = false;
		if ((containsLinePoint(l1, l2.getP1()) && containsLinePoint(l2, l1.getP2())) ||
			(containsLinePoint(l1, l2.getP2()) && containsLinePoint(l2, l1.getP2())) ||
			(containsLinePoint(l1, l2.getP1()) && containsLinePoint(l2, l1.getP1())) ||
			(containsLinePoint(l1, l2.getP2()) && containsLinePoint(l2, l1.getP1())))
			b = true;

//		if (b == false)
//			return b;
//
//		Line ll1 = new Line(l1.getEndPointTo(l2), l1.getCommonPointTo(l2));
//		Line ll2 = new Line(l2.getEndPointTo(l1), l2.getCommonPointTo(l1));
//		return equalAngle(angleDLineLine(ll1, ll2), Math.PI);
		return b && !nearPointPoint(l1.getCommonPointTo(l2), l2.getCommonPointTo(l1));
	}

	public static boolean joinsLineLine(Line l1, Line l2) {
		/*
		 * When l1 joins l2
		 * then make the joining point to be
		 * l1.epn() and l2.spn()
		 *
		 * This rule MUST be applied
		 *
		 * FAILED!!!
		 */
		if (nearPointPoint(l1.getP2(), l2.getP1()) &&
				!nearPointPoint(l1.getP1(), l2.getP2()) ) {
			return true;
		}

		if (nearPointPoint(l1.getP1(), l2.getP1()) &&
			!nearPointPoint(l1.getP2(), l2.getP2()) ) {
//			l1.switchPoints();
			return true;
		}

		if (nearPointPoint(l1.getP2(), l2.getP2()) &&
				!nearPointPoint(l1.getP1(), l2.getP1()) ) {
//			l2.switchPoints();
			return true;
		}

		if (nearPointPoint(l1.getP1(), l2.getP2()) &&
				!nearPointPoint(l1.getP2(), l2.getP1()) ) {
//			l1.switchPoints();
//			l2.switchPoints();
			return true;
		}

		return false;
	}

	public static double angleLineLine(Line l1, Line l2) {
		/*
		 * angle is between l1.dir() and l2.dir (0 ~ PI)
		 */
		double angle = angleDLineLine(l1, l2);
		if (angle > Math.PI)
			angle = angle - Math.PI;
		return angle;
	}

	public static boolean acuteAngleLineLine(Line l1, Line l2) {
		double angle = angleLineLine(l1, l2);
		return (Math.abs(angle) > 0 + ShapeThreshold.angleDis) &&
			   (Math.abs(angle) < Math.PI / 2 - ShapeThreshold.angleDis);
	}

	public static boolean acuteAngleDLineLine(Line l1, Line l2) {
		double angle = angleDLineLine(l1, l2);
		return (Math.abs(angle) < (Math.PI / 2) - ShapeThreshold.angleDis) &&
			   (Math.abs(angle) > 0 + ShapeThreshold.angleDis);
	}

	public static boolean obtuseAngleLineLine(Line l1, Line l2) {
		double angle = angleLineLine(l1, l2);
		return (Math.abs(angle) > Math.PI / 2 + ShapeThreshold.angleDis) &&
			   (Math.abs(angle) < Math.PI - ShapeThreshold.angleDis);
	}

	public static boolean obtuseAngleDLineLine(Line l1, Line l2) {
		double angle = angleDLineLine(l1, l2);
		return (Math.abs(angle) > Math.PI / 2 + ShapeThreshold.angleDis) &&
			   (Math.abs(angle) < Math.PI - ShapeThreshold.angleDis);
	}

	public static double angleDLineLine(Line l1, Line l2) {
		/*
		 * angleD is from l2.dir() to l1.dir() by
		 * anti-clockwise direction (0 ~ 2PI)
		 */

		return l1.dir().angleWith(l2.dir());
	}

//	public static boolean acuteAngleDLineLine(Line l1, Line l2) {
//		double angle = angleDLineLine(l1, l2);
//		return (angle > 0 + ShapeThreshold.angleDis) &&
//			   (angle < Math.PI / 2 - ShapeThreshold.angleDis);
//	}
//
//	public static boolean obtuseAngleDLineLine(Line l1, Line l2) {
//		double angle = angleDLineLine(l1, l2);
//		return (angle > Math.PI / 2 + ShapeThreshold.angleDis) &&
//			   (angle < Math.PI - ShapeThreshold.angleDis);
//	}

	public static boolean intersectsLineLine(Line l1, Line l2) {
		return l1.intersectsLine(l2);
	}

	public static boolean meetsLineLine(Line l1, Line l2) {
		return intersectsLineLine(l1, l2) ||
			   joinsLineLine(l1, l2) ||
			   containsLinePoint(l1, l2.getP1()) ||
			   containsLinePoint(l1, l2.getP2()) ||
			   containsLinePoint(l2, l1.getP1()) ||
			   containsLinePoint(l2, l1.getP2());
	}

	/*
	 * Line - Point
	 */
	public static boolean containsLinePoint(Line line, Point point) {
		return line.getBound(ShapeThreshold.lengthDis).contains(point.toPoint2D());
	}

	public static boolean joinsLinePoint(Line line, Point point) {
		return nearPointPoint(line.getP1(), point) ||
			   nearPointPoint(line.getP2(), point);
	}

	public static boolean collinearLinePoint(Line line, Point point) {
		return line.ptLineDist(point) < ShapeThreshold.lengthDis;
	}

	/*
	 * Point - Point
	 */
	public static boolean identicalPointPoint(Point p1, Point p2) {
		return p1.distance(p2) < ShapeThreshold.lengthIdentical;
	}

	public static boolean nearPointPoint(Point p1, Point p2) {
		return p1.distance(p2) < ShapeThreshold.lengthDis;
	}

	public static boolean abovePointPoint(Point p1, Point p2) {
		return p1.getY() < p2.getY();
	}

	public static boolean belowPointPoint(Point p1, Point p2) {
		return p1.getY() > p2.getY();
	}

	public static boolean leftPointPoint(Point p1, Point p2) {
		return p1.getX() < p2.getX();
	}

	public static boolean rightPointPoint(Point p1, Point p2) {
		return p1.getX() > p2.getX();
	}

	public static boolean aboveLeftPointPoint(Point p1, Point p2) {
		return abovePointPoint(p1, p2) && leftPointPoint(p1, p2);
	}

	public static boolean aboveRightPointPoint(Point p1, Point p2) {
		return abovePointPoint(p1, p2) && rightPointPoint(p1, p2);
	}

	public static boolean belowLeftPointPoint(Point p1, Point p2) {
		return belowPointPoint(p1, p2) && leftPointPoint(p1, p2);
	}

	public static boolean belowRightPointPoint(Point p1, Point p2) {
		return belowPointPoint(p1, p2) && rightPointPoint(p1, p2);
	}

	/*
	 * Triangle - Line
	 */
	public static boolean attachedTrianglePoint(Triangle tri, Point point) {
		return tri.getBound(ShapeThreshold.lengthDis).contains(point.toPoint2D()) &&
		!tri.getBound(-ShapeThreshold.lengthDis).contains(point.toPoint2D());
	}

	public static boolean attachedTriangleLine(Triangle tri, Line line) {
		return (attachedTrianglePoint(tri, line.getP1()) && !attachedTrianglePoint(tri, line.getP2())) ||
		(!attachedTrianglePoint(tri, line.getP1()) && attachedTrianglePoint(tri, line.getP2()));
	}

	public static boolean overlappedTriangleLine(Triangle tri, Line line) {
		return attachedTrianglePoint(tri, line.getP1()) &&
		attachedTrianglePoint(tri, line.getP2());
	}

	public static boolean attachedByVertexTriangleLine(Triangle tri, Line line) {
		if (!attachedTriangleLine(tri, line))
			return false;

		Point p = line.getP1();
		if (!attachedTrianglePoint(tri, p))
			p = line.getP2();

		if (nearPointPoint(tri.getP1(), p) ||
			nearPointPoint(tri.getP2(), p) ||
			nearPointPoint(tri.getP3(), p))
			return true;

		return false;
	}

	public static boolean attachedByEdgeTriangleLine(Triangle tri, Line line) {
		if (!attachedTriangleLine(tri, line))
			return false;

		Point p = line.getP1();
		if (!attachedTrianglePoint(tri, p))
			p = line.getP2();

		if (nearPointPoint(tri.getP1(), p) ||
			nearPointPoint(tri.getP2(), p) ||
			nearPointPoint(tri.getP3(), p))
			return false;

		return true;
	}

	/*
	 * Rectangle - Point
	 */
	public static boolean containsRectanglePoint(Rectangle rect, Point point) {
		return rect.getBound(ShapeThreshold.lengthDis).contains(point.toPoint2D());
	}

	public static boolean excludesRectanglePoint(Rectangle rect, Point point) {
		return !rect.getBound(-ShapeThreshold.lengthDis).contains(point.toPoint2D());
	}

	public static boolean onPointRectangle(Point point, Rectangle rect) {
		return containsRectanglePoint(rect, point) &&
			   excludesRectanglePoint(rect, point);
	}

	public static boolean offPointRectangle(Point point, Rectangle rect) {
		return !containsRectanglePoint(rect, point);
	}

	public static boolean containsRectanglePoint(Diamond rect, Point point) {
		return rect.getBound(ShapeThreshold.lengthDis).contains(point.toPoint2D());
	}

	public static boolean excludesRectanglePoint(Diamond rect, Point point) {
		return !rect.getBound(-ShapeThreshold.lengthDis).contains(point.toPoint2D());
	}

	public static boolean onPointRectangle(Point point, Diamond rect) {
		return containsRectanglePoint(rect, point) &&
			   excludesRectanglePoint(rect, point);
	}

	public static boolean offPointRectangle(Point point, Diamond rect) {
		return !containsRectanglePoint(rect, point);
	}

	/*
	 * Rectangle - Line
	 */
	public static boolean connectsRectangleLine(Rectangle rect, Line line) {
		return
		(onPointRectangle(line.getP1(), rect) && offPointRectangle(line.getP2(), rect)) ||
		(onPointRectangle(line.getP2(), rect) && offPointRectangle(line.getP1(), rect));
	}

	public static boolean containsRectangleLine(Rectangle rect, Line line) {
		return containsRectanglePoint(rect, line.getP1()) &&
			   containsRectanglePoint(rect, line.getP2());
	}

	public static boolean excludesRectangleLine(Rectangle rect, Line line) {
		return excludesRectanglePoint(rect, line.getP1()) &&
			   excludesRectanglePoint(rect, line.getP2());
	}

	public static boolean connectsRectangleLine(Diamond rect, Line line) {
		return
		(onPointRectangle(line.getP1(), rect) && offPointRectangle(line.getP2(), rect)) ||
		(onPointRectangle(line.getP2(), rect) && offPointRectangle(line.getP1(), rect));
	}

	public static boolean containsRectangleLine(Diamond rect, Line line) {
		return containsRectanglePoint(rect, line.getP1()) &&
			   containsRectanglePoint(rect, line.getP2());
	}

	public static boolean excludesRectangleLine(Diamond rect, Line line) {
		return excludesRectanglePoint(rect, line.getP1()) &&
			   excludesRectanglePoint(rect, line.getP2());
	}

	/*
	 * LinkEnd - Line
	 */
	public static boolean meetsLinkEndLine(LinkEnd linkEnd, Line line) {
		for (Line each : linkEnd.getPath()) {
			if (meetsLineLine(each, line))
				return true;
		}
		return false;
	}

	/*
	 * LinkEnd - LinkEnd
	 */
	public static boolean meetsLinkEndLinkEnd(LinkEnd le1, LinkEnd le2) {
		for (Line l1 : le1.getPath()) {
			for (Line l2 : le2.getPath()) {
				if (meetsLineLine(l1, l2))
					return true;
			}
		}
		return false;
	}

	/*
	 * Link - LinkEnd
	 */
	public static boolean meetsLinkLinkEnd(Link link, LinkEnd linkEnd) {
		for (LinkEnd le : link.getLinkEnds()) {
			if (meetsLinkEndLinkEnd(le, linkEnd))
				return true;
		}
		return false;
	}
}
